/* Copyright 2012 Dustin Mitchell <dmmitche [at] gmail [dot] com>
 *
 * Licensed under the GPLv3.  See included LICENSE.txt for full details.
 */

#include <stdlib.h>

#include "list.h"

struct list_node
{
  struct list_node *prev;
  struct list_node *next;
  void *data;
};

static void list_iter_next (struct iter *);
static void list_iter_prev (struct iter *);
static int list_iter_compare (struct iter *, struct iter *);
static void * list_iter_deref (struct iter *);

/* Linked list iterator */
struct iter_ops list_iter_ops = {
  .next = list_iter_next,
  .prev = list_iter_prev,
  .compare = list_iter_compare,
  .deref = list_iter_deref
};



static int list_iter_create (struct list_iter *iter, struct list_node *node)
{
  if (!iter)
    return -1;

  if (iter_create (&iter->base, &list_iter_ops) == -1)
    return -1;

  iter->node = node;

  return 0;
}

void list_iter_next (struct iter *iter)
{
  struct list_iter *liter = (struct list_iter *)iter;

  if (!liter)
    return;

  if (!liter->node)
    return;

  liter->node = liter->node->next;
}

void list_iter_prev (struct iter *iter)
{
  struct list_iter *liter = (struct list_iter *)iter;

  if (!liter)
    return;

  if (!liter->node)
    return;

  liter->node = liter->node->prev;
}

int list_iter_compare (struct iter *iter1, struct iter *iter2)
{
  struct list_iter *liter1 = (struct list_iter *)iter1;
  struct list_iter *liter2 = (struct list_iter *)iter2;

  if (!liter1 || !liter2)
    return 0;

  return liter1->node == liter2->node ? 1 : 0;
}

void * list_iter_deref (struct iter *iter)
{
  struct list_iter *liter = (struct list_iter *)iter;

  if (!liter)
    return NULL;

  if (!liter->node)
    return NULL;

  return liter->node->data;
}



int list_create (struct list *list)
{
  if (!list)
    return -1;

  list->head = list->tail = NULL;

  return 0;
}

void list_destroy (struct list *list)
{
  if (!list)
    return;

  while (list->head)
    list_pop_front (list);
}

int list_insert (struct list *list, void *data, struct list_iter *pos)
{
  struct list_node *new_node;
  struct list_node *left_node;
  struct list_node *right_node;

  if (!list || !pos)
    return -1;

  new_node = (struct list_node *)malloc (sizeof (struct list_node));
  if (!new_node)
    return -1;

  new_node->data = data;
  new_node->next = new_node->prev = NULL;

  if (!pos->node) {
    left_node = list->tail;
    right_node = NULL;
  }
  else {
    left_node = pos->node->prev;
    right_node = pos->node;
  }

  if (!left_node && !right_node) {
    list->head = list->tail = new_node;
  }
  else if (!left_node) {
    new_node->next = list->head;
    list->head->prev = new_node;
    list->head = new_node;
  }
  else if (!right_node) {
    new_node->prev = list->tail;
    list->tail->next = new_node;
    list->tail = new_node;
  }
  else {
    left_node->next = new_node;
    right_node->prev = new_node;
    new_node->prev = left_node;
    new_node->next = right_node;
  }

  pos->node = new_node;

  return 0;
}

int list_remove (struct list *list, struct list_iter *pos)
{
  struct list_node *rem_node;
  struct list_node *left_node;
  struct list_node *right_node;

  if (!list || !pos)
    return -1;

  if (!pos->node) {
    left_node = list->tail->prev;
    right_node = NULL;
  }
  else {
    left_node = pos->node->prev;
    right_node = pos->node->next;
  }

  if (!left_node && !right_node) {
    rem_node = list->head;
    list->head = list->tail = NULL;
  }
  else if (!left_node) {
    rem_node = list->head;
    list->head = right_node;
    list->head->prev = NULL;
  }
  else if (!right_node) {
    rem_node = list->tail;
    list->tail = left_node;
    list->tail->next = NULL;
  }
  else {
    rem_node = pos->node;
    left_node->next = right_node;
    right_node->prev = left_node;
  }

  free (rem_node);

  pos->node = right_node;

  return 0;
}

int list_push_front (struct list *list, void *data)
{
  struct list_iter pos;

  if (list_begin (list, &pos) == -1)
    return -1;

  return list_insert (list, data, &pos);
}

int list_push_back (struct list *list, void *data)
{
  struct list_iter pos;

  if (list_end (list, &pos) == -1)
    return -1;

  return list_insert (list, data, &pos);
}

int list_pop_front (struct list *list)
{
  struct list_iter pos;

  if (list_begin (list, &pos) == -1)
    return -1;

  return list_remove (list, &pos);
}

int list_pop_back (struct list *list)
{
  struct list_iter pos;

  if (list_end (list, &pos) == -1)
    return -1;

  return list_remove (list, &pos);
}

void * list_front (struct list *list)
{
  if (!list || !list->head)
    return NULL;

  return list->head->data;
}

void * list_back (struct list *list)
{
  if (!list || !list->tail)
    return NULL;

  return list->tail->data;
}

int list_empty (struct list *list)
{
  if (!list)
    return -1;

  if (list->head == NULL)
    return 1;

  return 0;
}

int list_begin (struct list *list, struct list_iter *iter)
{
  if (!list || !iter)
    return -1;

  return list_iter_create (iter, list->head);
}

int list_end (struct list *list, struct list_iter *iter)
{
  if (!list || !iter)
    return -1;

  return list_iter_create (iter, NULL);
}
